
package com.probiz.estore.core.search;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.web.bind.ServletRequestUtils;

import com.probiz.estore.common.util.DateConverter;
import com.probiz.estore.core.controller.ControllerConstants;
import com.probiz.estore.core.model.BaseObject;

/**
 * 从Request自动构造SearchCriteria的工具类，目前支持HQL，可以扩展支持Criteria。
 * <P>
 * 由于需要支持Filter，HQL无法只是根据Request就确定下来，需要在DAO获得Filter的Hql后才能最终确定，因此需要先用Filter取得Hql，再根据Request创建。
 * 
 */
public class SearchCriteriaBuilder {
	
	private final static transient Log	logger	= LogFactory.getLog(SearchCriteriaBuilder.class);
	//表字段类型
	public final static String DATATYPE_STRING = "String";
	public final static String DATATYPE_INTEGER = "Integer";
	public final static String DATATYPE_SHORT = "Short";
	public final static String DATATYPE_BIGDECIMAL = "BigDecimal";
	public final static String DATATYPE_DATE = "Date";
	public final static String DATATYPE_DATE_BEGIN = "Date_Begin";
	public final static String DATATYPE_DATE_END = "Date_End";
	
	//where表达式比较子
	public final static String EXP_EQ = "EQ";
	public final static String EXP_LIKE = "LIKE";
	public final static String EXP_NOT = "NOT";
	public final static String EXP_GTE = "GTE";
	public final static String EXP_GT = "GT";
	public final static String EXP_LTE = "LTE";
	public final static String EXP_LT = "LT";
	
	
	/**
	 * 可选择的hql枚举
	 */
	private Enum				filterName				= BaseObject.FilterHqlName.DEFAULT;
	
	/**
	 * 查询HQL
	 */
	private String				hql						= "";

	/**
	 * 用于储存页面传入的参数及其原来的值，可以用来重现－编辑等，这样保存的SC与新的都通过统一方式操作
	 */
		private Map<String, Object>	paramMap				= new LinkedHashMap<String, Object>();
	/**
	 * 用于储存分析/转换过的值，目的是传给Hql用，代替？参数；除了储存Where子句的参数，也储存Order by子句的参数。
	 */
	private List<Object>		paramValueList			= null;
	/**
	 * select子句
	 */
	private String				selectClause			= "";
	/**
	 * form子句
	 */
	private String				fromClause				= "";
	/**
	 * where子句
	 */
	private StringBuffer		whereClause				= new StringBuffer();
	/**
	 * 用户指定的order by子句
	 */
	private String				orderByClause			= "";
	
	/**
	 * 当用户没有指定排序字段的时候缺省的排序字段
	 */
	private String				defaultOrderByClause	= "";


	
	public final static SearchCriteriaBuilder getInstance(Enum filterName, String filterHql) {
		SearchCriteriaBuilder hqlBuilder = new SearchCriteriaBuilder();
		hqlBuilder.initBaseHql(filterHql);
		hqlBuilder.filterName = filterName;
		return hqlBuilder;
	}
	
	public final static SearchCriteriaBuilder getInstance(Enum filterName, String filterHql, Object[] initHqlParamValues) {
		SearchCriteriaBuilder hqlBuilder = new SearchCriteriaBuilder();
		hqlBuilder.initBaseHql(filterHql);
		hqlBuilder.filterName = filterName;
		if(!ArrayUtils.isEmpty(initHqlParamValues)){
			for(Object initHqlParamValue: initHqlParamValues)
			hqlBuilder.addParamValue(initHqlParamValue);
		}
		return hqlBuilder;
	}
	
	/**
	 * 根据request创建一个SearchCriteria对象
	 * 
	 * 原理：
	 * 如果paramer的名称以COL@开始则表示是where子句中的过滤条件 。paramer的名称 规定为  COL@columnName@dataType@operator ，columnName是字段名称， dataType是字段类型，operator是比较子
	 * 如果paramer的名称是SRH@orderBy，则表示是order by子句的内容
	 * 
	 * 例如request中有以下三个参数
	 * 1.paramName = COL@s.status@Short@EQ，  paramValue =2
	 * 表示要增加过滤条件where子句 表字段status等于2，其中status是Short类型； 即...where s.status = 2...
	 * 
	 * 2.paramName = COL@s.mesage@String@LIKE，  paramValue =ABC
	 * 表示要增加过滤条件到where子句 表字段message like '%ABC%'，其中message是String类型, 即...where s.mesage = '%ABC%'...
	 * 
	 * 3.paramName = SRH@orderBy，  paramValue = s.createTime desc
	 * 表示其orderBy子句内容是  order by s.createTime desc
	 * 
	 * @param request
	 * @param defaultPageSize
	 * @return
	 */
	public SearchCriteria buildSearchCriteria(HttpServletRequest request, int defaultPageSize) {
		
		int pageNo = ServletRequestUtils.getIntParameter(request, ControllerConstants.PRM_PAGENO, 1);
		if (pageNo < 1) {
			pageNo = 1;
		}
		int pageSize = ServletRequestUtils.getIntParameter(request, ControllerConstants.PRM_ITEMS_PER_PAGE, defaultPageSize);
		// 某些情况下需要取得全部数据，例如数据导出的时候，用NoPaging控制
		if (request.getParameter(ControllerConstants.NoPaging) != null) {
			pageSize = 0;
		}
		
		// 对于ShowAll，应作为一个filter的特例处理，例如disallow所有条件
		// 处理用户输入的动态查询条件，可选（可以没有动态的查询条件）
		Map requestParameterMap = request.getParameterMap();
		boolean isSearchCriteriaChanged = false;
		for (Iterator iter = requestParameterMap.keySet().iterator(); iter.hasNext();) {
			String paramName = (String) iter.next();
			String paramValue = ServletRequestUtils.getStringParameter(request, paramName, "").trim();

			if (paramName.startsWith("COL@")) {
				//这是一个过滤条件列，增加到where子句中
				if(StringUtils.isNotBlank(paramValue)){
					addFilterColumn(paramName, paramValue);
					isSearchCriteriaChanged = true;
				}
			} 
			
			paramMap.put(paramName, paramValue);
		}

		String orderBy = request.getParameter("SRH@orderBy");
		if(StringUtils.isNotBlank(orderBy)){
			orderByClause = " order by "+ orderBy; 
		}else{
			//还原
			orderByClause = null;
		}

		// 需要页面重新绑定的时候，需要保存paramMap
		SearchCriteria sc = SearchCriteria.getHqlPagingInstance(getHql(), getParamValues(), pageNo, pageSize, paramMap);
		sc.setSearchCriteriaChanged(isSearchCriteriaChanged);
		return sc;
	}
	
	/**
	 * 设置基本的Hql，用来初始化；并可以作为缺省的Hql（当没有参数的时候）
	 * 
	 * @param hql
	 */
	private void initBaseHql(String hql) {

		String upperHql = hql.toUpperCase();
		Assert.doesNotContain(upperHql, "GROUP BY", "Group by is not supported in Search Criteria Default Implementation. Implement your own!");
		Assert.doesNotContain(upperHql, " UNION ", "UNION is not supported in Search Criteria Default Implementation. Implement your own!");

		int idxSelect = upperHql.indexOf("SELECT ");
		int idxFrom = upperHql.indexOf("FROM ");
		int idxWhere = upperHql.indexOf(" WHERE ");
		int idxOrderBy = upperHql.indexOf(" ORDER BY ");

		Assert.isTrue(idxFrom > -1, "HQL must have FROM keyword!");

		if (idxSelect >= 0) {
			selectClause = hql.substring(idxSelect, idxFrom);
		}

		int idxEndOfFrom = (idxWhere > 0) ? idxWhere : (idxOrderBy > 0 ? idxOrderBy : hql.length());
		fromClause = hql.substring(idxFrom, idxEndOfFrom);
		Assert.hasText(fromClause, "Search criteria base hql invalid, from clause cannot be empty.");

		if (idxWhere > 0) {
			// 缺省的Where子句，而且即使有用户输入的查询条件，本条件也会添加上去。
			String defaultWhereClause = hql.substring(idxWhere, idxOrderBy > 0 ? idxOrderBy : hql.length());
			whereClause.append(defaultWhereClause);
		}

		if (idxOrderBy > 0) {
			defaultOrderByClause = hql.substring(idxOrderBy , hql.length());
		}

	}
	/**
	 * 增加一个过滤列条件
	 * 
	 * @param paramName  参数名，其格式规定为   COL@columnName@dataType@operator 
	 * dataType： 参考DATATYPE_为前缀的常量
	 * operator：参考EXP_为前缀的常量
	 * @param paramValue 参数值
	 * 
	 * 例如paramName = COL@s.status@Short@EQ，  paramValue =2
	 * 表示要增加过滤条件 表字段status等于2，其中status是Short类型
	 */
	private void addFilterColumn(final String paramName, final String paramValue) {
		if(StringUtils.isBlank(paramValue))return;
		

		StringTokenizer st = new StringTokenizer(paramName, "@");
		st.nextToken(); // is "COL@", skip only;
		
		//-----------------------设置columnName------------------------
		String columnName = st.nextToken();
		
		//-----------------------设置dataType------------------------
		String dataType = "String";// default
		if (st.hasMoreTokens()) {
			dataType = st.nextToken();
		}
		Object convertedValue = null;
		if (DATATYPE_STRING.equals(dataType)) {
			convertedValue = paramValue;
		} else if (DATATYPE_INTEGER.equals(dataType)) {
			convertedValue = new Integer(paramValue);
		} else if (DATATYPE_SHORT.equals(dataType)) {
			convertedValue = new Short(paramValue);
		} else if (DATATYPE_DATE.equals(dataType)) {
			DateConverter dateConverter = new DateConverter();
			convertedValue = dateConverter.convert(Date.class, paramValue);
		}else if (DATATYPE_DATE_BEGIN.equals(dataType)) {
			DateConverter dateConverter = new DateConverter();
			convertedValue = dateConverter.convert(Timestamp.class, paramValue+" 00:00:00");
		}else if (DATATYPE_DATE_END.equals(dataType)) {
			DateConverter dateConverter = new DateConverter();
			convertedValue = dateConverter.convert(Timestamp.class, paramValue+" 23:59:59");
		} else if (DATATYPE_BIGDECIMAL.equals(dataType)) {
			convertedValue = new BigDecimal(paramValue);
		} else {
			logger.warn("Found datatype not supported [check syntax needed]:" + dataType);
			convertedValue = paramValue;
		}
		//-----------------------设置operator------------------------
		
		String operator = " = ?";// default
		if (st.hasMoreTokens()) {
			String operatorMark = st.nextToken();
			
			if (EXP_EQ.equals(operatorMark)) {
				operator = " = ?";
			}else if (EXP_LIKE.equals(operatorMark)) {
				// dataType must be String
				operator = " like ?";
				convertedValue = "%" + paramValue + "%";
			} else if (EXP_NOT.equals(operatorMark)) {
				operator = " <> ?";
			} else if (EXP_GTE.equals(operatorMark)) {
				operator = " >= ?";
			} else if (EXP_GT.equals(operatorMark)) {
				operator = " > ?";
			} else if (EXP_LTE.equals(operatorMark)) {
				operator = " <= ?";
			} else if (EXP_LT.equals(operatorMark)) {
				operator = " < ?";
			} 
			// else use default: = ?
		}


		//-----------------------把条件添加到Where子句------------------------
		appendWhereCondition(columnName, operator);
		//-----------------------把HQL参数值添加到params------------------------
		addParamValue(convertedValue);
	}
	/**
	 * 把条件添加到Where子句
	 * @param column
	 * @param operator
	 */
	private void appendWhereCondition(String column, String operator) {
		if (whereClause.length() == 0) {
			whereClause.append("where ");
		} else {
			whereClause.append(" and ");
		}
		whereClause.append(column).append(operator);
	}
	

	/**
	 * 获得HQL
	 * @return
	 */
	private String getHql() {
		if (StringUtils.isBlank(hql)) {
			StringBuffer buf = new StringBuffer();
			buf.append(selectClause).append(" ");
			buf.append(fromClause).append(" ");
			buf.append(whereClause).append(" ");
			if(StringUtils.isNotBlank(orderByClause)){
				buf.append(orderByClause).append(" ");
			}else if(StringUtils.isNotBlank(defaultOrderByClause)){
				buf.append(defaultOrderByClause).append(" ");
			}
			hql = StringUtils.replace(buf.toString(), "  ", " ");
		}
		return hql;
	}
	
	/**
	 * 增加HQL参数值
	 * 一般情况下不需调用这个方法，应使用构造子
	 * @see SearchCriteriaBuilder getInstance(Enum filterName, String filterHql, Object[] initHqlParamValues)
	 * @param paramValue
	 */
	private void addParamValue(Object paramValue){
		if(null == paramValueList){
			paramValueList = new ArrayList<Object>(); 
		}
		paramValueList.add(paramValue);
	}
	/**
	 * 获得HQL参数数组
	 * @return
	 */
	private Object[] getParamValues() {
		return paramValueList == null ? new Object[0] : paramValueList.toArray();
	}
	
}
